﻿#region Copyright Statement
//  ------------------------------------------------------------------------------
//  
//    This file is part of The Streambolics Library.
//    Copyright © 2005-2009, Stephan Leclercq & Streambolics
//    All rights reserved.
//  
//    The Streambolics Library is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 2 of the License, or
//    (at your option) any later version.
//  
//    The Streambolics Library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//  
//    You should have received a copy of the GNU General Public License
//    along with Streambolics; if not, write to the Free Software
//    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//  
//  ------------------------------------------------------------------------------
#endregion

using System;
using System.Collections.Generic;
using System.Text;


namespace Streambolics.PatLite
{
    /// <summary>
    ///     A real PHC device.
    /// </summary>
    /// <remarks><para>
    ///     The class maintains the wanted PhcStatus, and the known
    ///     PhcStatus. When both agree, then the real device is in the
    ///     appropriate state. Otherwise, a command needs to be sent to
    ///     the device for its state to change.
    /// </para></remarks>

    public class PhcDevice : PhcStatus, IMessageConsumer, IMessageProducer
    {
        private PhcStatus _KnownStatus = new PhcStatus ();
        private PhcStatus _NextStatus;
        private DateTime _MessageTimeout;
        private bool _Reset;
        private DateTime _AutoResume;

        /// <summary>
        ///     Creates a new PhcDevice.
        /// </summary>
        
        public PhcDevice ()
        {
        }

        #region Message creation utilities

        /// <summary>
        ///     Creates an empty message to be sent to the PHC.
        /// </summary>
        /// <param name="aForSet">
        ///     If true, create a "Set" message, otherwise create
        ///     a "Reset" message.
        /// </param>
        /// <returns>
        ///     Tha array of bytes representing the message.
        /// </returns>

        public byte[] GetEmptyMessage (bool aForSet)
        {
            return new byte[] { 0x40, 0x3F, 0x3F, (byte)(aForSet?0x31:0x30), 0x30, 0x30, 0x21};
        }

        private void FillBit (int bit, Ternary aTargetValue, PhcStatus aTargetStatus, byte[] aTargetMessage)
        {
            if (this[bit] == aTargetValue && aTargetStatus[bit] != aTargetValue)
            {
                aTargetStatus[bit] = aTargetValue;
                int targetByte, targetBit;

                if (bit > 3)
                {
                    targetByte = 4;
                    targetBit = bit - 4;
                }
                else
                {
                    targetByte = 5;
                    targetBit = bit;
                }
                aTargetMessage[targetByte] |= (byte)(1 << targetBit);           
            }
        }

        /// <summary>
        ///     Intercepted to auto-resume communication.
        /// </summary>
        /// <param name="e">
        ///     The event, unused.
        /// </param>

        protected override void OnChanged (EventArgs e)
        {
            if (_Reset)
            {
                Resume ();
            }
            base.OnChanged (e);
        }

        /// <summary>
        ///     Resume operation.
        /// </summary>
        /// <remarks><para>
        ///     This function marks the status of the PHC as unknown,
        ///     so all commands are sent back to put it in the wanted
        ///     status.
        /// </para><para>
        ///     This function is called automatically when the user has
        ///     reset the PHC by the reset button, either after another
        ///     status needs to be changed, or after a 2 minutes timeout.
        /// </para></remarks>

        public void Resume ()
        {
            _Reset = false;
            _KnownStatus.SetUnknown ();
        }

        #endregion

        #region ICommunicationHandler implementation

        /// <summary>
        ///     The next message to send to the PHC.
        /// </summary>
        /// <returns>
        ///     An array of bytes corresponding to the command to send
        ///     to the PHC.
        /// </returns>

        public byte[] ProduceMessage ()
        {
            if (_Reset && DateTime.Now >= _AutoResume)
            {
                Resume ();
            }
            if (_NextStatus == null)
            {
                if (SameAs (_KnownStatus) != Ternary.False)
                {
                    return null;
                }

                PhcStatus SetStatus = new PhcStatus(_KnownStatus);
                byte[] SetMessage = GetEmptyMessage (true);

                PhcStatus ResetStatus = new PhcStatus (_KnownStatus);
                byte[] ResetMessage = GetEmptyMessage(false);

                for (int i = 0; i < 8; i++)
                {
                    FillBit (i, Ternary.True, SetStatus, SetMessage);
                    FillBit (i, Ternary.False, ResetStatus, ResetMessage);
                }
                if (_KnownStatus.SameAs (ResetStatus) != Ternary.False)
                {
                    if (_KnownStatus.SameAs (SetStatus) != Ternary.False)
                    {
                        return null;
                    }
                    _NextStatus = SetStatus;
                    _MessageTimeout = DateTime.Now.AddSeconds (10);
                    return SetMessage;
                }
                _NextStatus = ResetStatus;
                _MessageTimeout = DateTime.Now.AddSeconds (10);
                return ResetMessage;

            }
            else
            {
                if (DateTime.Now > _MessageTimeout)
                {
                    _NextStatus = null;
                    _KnownStatus.SetUnknown ();
                }
                return null;
            }
        }

        /// <summary>
        ///     Interpret the message received from the peer.
        /// </summary>
        /// <param name="aMessage">
        ///     The buffer containing the text of the message.
        /// </param>
        /// <param name="aLength">
        ///     The size of the message within the buffer.
        /// </param>

        public void ConsumeMessage (byte[] aMessage)
        {
            if (aMessage.Length > 0)
            {
                switch (aMessage[0])
                {
                    case 6:
                        if (_NextStatus != null)
                        {
                            _KnownStatus.Set (_NextStatus);
                        }
                        _NextStatus = null;
                        break;
                    case 21:
                        _NextStatus = null;
                        _KnownStatus.SetUnknown ();
                        break;
                }
            }
        }

        /// <summary>
        ///     Indicates that the device was manually reset by the user.
        /// </summary>

        public void PeerReset ()
        {
            _Reset = true;
            _AutoResume = DateTime.Now.AddMinutes (2);
        }

        #endregion



    }
}
